#pragma once

#include "RdcBuffer.h"
#include "RdcTransport.h"
#include "VcClientUtils.h"
#include "ClientChannelManager.h"

#include <windows.h>
#include <string>

#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>

namespace rdc_transport
{
    struct ICliChannalCore
    {
        virtual ~ICliChannalCore(){};
        virtual void ChannelInitEvent(LPVOID pInitHandle, UINT event, LPVOID pData, UINT dataLength) throw() = 0;
        virtual void ChannelOpenEvent(DWORD openHandle, UINT event, LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) throw() = 0;
    };

    template<class ConnectionDispatch, class DataHandler>
    class ClientChannelCore 
        : private boost::noncopyable
        , public ICliChannalCore
        , public IVirtChannel
    {
    public:
        struct CreatinonParams 
        {
            CreatinonParams(const std::string& name, ULONG properties, PCHANNEL_ENTRY_POINTS ePoints)
                : channelName(name)
                , channelProperies(properties)
                , entryPoints(ePoints)
            {}

            std::string channelName;
            ULONG channelProperies;
            PCHANNEL_ENTRY_POINTS entryPoints;
        };
            
        struct CoreState
        {
           CoreState()
               : complete_(false) , errorCode_(0)
           {}

           bool complete_;
           UINT errorCode_;
        };

    private:
        typedef std::auto_ptr<ConnectionDispatch> pConnectionDispatch;
        typedef std::auto_ptr<DataHandler> pDadaHandler;

    public:
        ClientChannelCore(CreatinonParams& params, pConnectionDispatch cs, pDadaHandler dh);
        ~ClientChannelCore();

    public: // Be careful using this method directly in your code.
        void Write(Buffer* buffer);

    private: // Callback methods from mstsc
        void ChannelInitEvent(LPVOID pInitHandle, UINT event, LPVOID pData, UINT dataLength) throw();
        void ChannelOpenEvent(DWORD openHandle, UINT event, LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) throw();

    private:
        void OpenChannel(LPVOID pInitHandle) throw();
        void CloseChannel() throw();

    private:
        boost::shared_ptr<CHANNEL_ENTRY_POINTS> m_pChannelEntryPoints;
        DWORD m_channelHandle;
        std::string m_channelName;
        CoreState m_state;

        pConnectionDispatch m_connectionDispatch;
        pDadaHandler m_dataHandler;
    };

    //////////////////////////////////////////////////////////////////////////

    
    template<class ConnectionDispatch, class DataHandler>
    rdc_transport::ClientChannelCore<ConnectionDispatch, DataHandler>::ClientChannelCore(CreatinonParams& params, pConnectionDispatch cd, pDadaHandler dh)
        : m_pChannelEntryPoints(rdc_transport::MakeCopy(params.entryPoints))
        , m_channelHandle(0)
        , m_channelName(params.channelName)
        , m_connectionDispatch(cd)
        , m_dataHandler(dh)
    {
        rdc_transport::InitChannel(params.entryPoints, params.channelName.c_str(), params.channelProperies,(PCHANNEL_INIT_EVENT_FN)ChannelInitEventProc);
        rdc_transport::GetClientChannelManager()->AddChannel(this);
    }

    template<class ConnectionDispatch, class DataHandler>
    rdc_transport::ClientChannelCore<ConnectionDispatch, DataHandler>::~ClientChannelCore()
    {
        rdc_transport::GetClientChannelManager()->DeleteChannel(this);
    }

    template<class ConnectionDispatch, class DataHandler>
    void rdc_transport::ClientChannelCore<ConnectionDispatch, DataHandler>::ChannelInitEvent(LPVOID pInitHandle, UINT event, LPVOID pData, UINT dataLength) throw()
    {
        if(event == CHANNEL_EVENT_CONNECTED)
            OpenChannel(pInitHandle);
        if(event == CHANNEL_EVENT_DISCONNECTED)
            CloseChannel();
            
        // Appropriate event signaled. All slots will receive notification.
        m_connectionDispatch->OnEvent(event)(pInitHandle, pData, dataLength);
    }

    template<class ConnectionDispatch, class DataHandler>
    void rdc_transport::ClientChannelCore<ConnectionDispatch, DataHandler>::ChannelOpenEvent(DWORD openHandle, UINT event, LPVOID pData, UINT32 dataLength, UINT32 totalLength, UINT32 dataFlags) throw()
    {
        if (openHandle == m_channelHandle)
        {
            // Appropriate event signaled. All slots will receive notification.
            m_dataHandler->OnEvent(event)(pData, dataLength, totalLength, dataFlags);
        }
    }
  
    template<class ConnectionDispatch, class DataHandler>
    void rdc_transport::ClientChannelCore<ConnectionDispatch, DataHandler>::OpenChannel(LPVOID pInitHandle) throw()
    {
        DWORD dwOpenChannel;
        UINT uiRes = m_pChannelEntryPoints->pVirtualChannelOpen(
            pInitHandle,
            &dwOpenChannel,
            const_cast<PCHAR>(m_channelName.c_str()),
            (PCHANNEL_OPEN_EVENT_FN)ChannelOpenEventProc);

        if (uiRes == CHANNEL_RC_OK)
        {
            m_channelHandle = dwOpenChannel;
            m_state.complete_ = true;
        }
        else
        {
            m_state.complete_ = false;
            m_state.errorCode_ = uiRes;
        }
    }
    
    template<class ConnectionDispatch, class DataHandler>
    void rdc_transport::ClientChannelCore<ConnectionDispatch, DataHandler>::CloseChannel() throw()
    {
        m_pChannelEntryPoints->pVirtualChannelClose(m_channelHandle);
        m_state.complete_ = false;
    }

    template<class ConnectionDispatch, class DataHandler>
    void rdc_transport::ClientChannelCore<ConnectionDispatch, DataHandler>::Write(Buffer* buffer)
    {
        if(!m_state.complete_)
            throw std::runtime_error(std::string(__FUNCTION__) + " VirtualChannel core is in complete state ");

        DWORD dwControlCode = 0;
        UINT uiRes = m_pChannelEntryPoints->pVirtualChannelWrite(
            m_channelHandle,
            &(buffer->at(0)),
            buffer->size(),
            (LPVOID)&dwControlCode);

        if (uiRes != CHANNEL_RC_OK)
            throw std::runtime_error(std::string(__FUNCTION__) + " VirtualChannelWrite failed ");
    }
}